//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2006
//
// The helper to fill editbox with file content.
//

# include "StdAfx.h"
# include "Resource.h"
# include "ShowFile.h"
# include "Wrappers.h"
# include "ParseHelpers.h"
# include "Globals.h"

static
void AddHex8 (BYTE _bt, char * _str) {
	const size_t c0 = _bt >> 4;
	_str [0] = c0 > 9 ? (c0 - 10) + 'A' : c0 + '0';
	const size_t c1 = _bt & 0xF;
	_str [1] = c1 > 9 ? (c1 - 10) + 'A' : c1 + '0';
}

const size_t c_cWINDOW = 32768; // NOTE: under Win9x, at most 0xFFFF characters per edit control is possible.

static
size_t _Line2Offset (HANDLE _hFile, size_t _cLine) {
	if (_cLine <= 1)
		return 0;

	const DWORD dwFileSize = ::GetFileSize (_hFile, NULL);
	if (dwFileSize == 0)
		return 0;
	BYTE * const pData = new BYTE [dwFileSize];
	CByteArrayHolder pData_Holder (pData);

	DWORD dwFileSizeRead = 0;
	::ReadFile (_hFile, pData, dwFileSize, & dwFileSizeRead, NULL);
	if (dwFileSizeRead < dwFileSize)
		return 0;

	size_t cLine = 1;
	for (size_t c = 0; c < dwFileSize; ++ c) {
		if (pData [c] == '\n') {
			++ cLine;
			if (cLine == _cLine)
				return c + 1 < dwFileSize ? c + 1 : c;
		}
	}

	return dwFileSize;
}

static
void _ShowFileCtl (HWND _hWnd, int _id, const char * _strFileName, bool _bLine2Offset, size_t _cOffsetOrLine, size_t _cLen) {
	::SendMessage (::GetDlgItem (_hWnd, _id), WM_SETFONT, (WPARAM) g_hFixedFont, FALSE);

	if (_cLen == 0) {
		::SetDlgItemText (_hWnd, _id, "(No data.)");
		return;
	}

	// Open the file.
	const CFileForRead hFile (_strFileName);
	if (! hFile.IsValid ()) {
		::SetDlgItemText (_hWnd, _id, "(Can't open the file.)");
		return;
	}

	// Read the file part.
	const DWORD dwSize = ::GetFileSize (hFile, NULL);

	const size_t cOffset = _bLine2Offset ? _Line2Offset (hFile, _cOffsetOrLine) : _cOffsetOrLine;

	size_t cBeginOffset, cEndOffset;
	if (_cLen == -1) {
		cBeginOffset = cOffset > c_cWINDOW ? cOffset - c_cWINDOW : 0;
		cEndOffset = cOffset + c_cWINDOW > dwSize ? dwSize : cOffset + c_cWINDOW;
	} else {
		cBeginOffset = cOffset;
		cEndOffset = cOffset + _cLen;
	}
	assert (cEndOffset >= cBeginOffset);
	const size_t cDataSize = cEndOffset - cBeginOffset;

	char * const pSource = new char [cDataSize + 1];
	CArrayHolder<char> pSource_Holder (pSource);

	::SetFilePointer (hFile, cBeginOffset, 0, FILE_BEGIN);
	DWORD dwFileSizeRead = 0;
	::ReadFile (hFile, pSource, cDataSize, & dwFileSizeRead, NULL);
	if (dwFileSizeRead < cDataSize) {
		::SetDlgItemText (_hWnd, _id, "(Can't read the file.)");
		return;
	}
	pSource [cDataSize] = 0;

	if (IsBinary (pSource, pSource + cDataSize)) {
		//
		// Binary file.
		//

		const size_t cLines = cDataSize/16 + 2;
		const size_t cLineLen = 8 + 3 + 3*16 + 2 + 16 + 2;
		char * const strHexData = new char [cLineLen*cLines + 1];
		CArrayHolder<char> strHexData_Holder (strHexData);

		// Fill strHexData with hex content.
		{
			size_t cPos = 0;
			size_t cOffset = (cBeginOffset/16)*16; 
			while (cOffset <= (cEndOffset/16)*16) {
				// Offset.
				AddHex8 (cOffset >> 24, strHexData + cPos); cPos += 2;
				AddHex8 (cOffset >> 16, strHexData + cPos);	cPos += 2;
				AddHex8 (cOffset >> 8,  strHexData + cPos);	cPos += 2;
				AddHex8 (cOffset,       strHexData + cPos);	cPos += 2;

				// Delimiter.
				strHexData [cPos ++] = ':';
				strHexData [cPos ++] = ' ';
				strHexData [cPos ++] = ' ';

				char * const strText = strHexData + cPos + 3*16 + 2;
				for (size_t c = 0; c < 16; ++ c) {
					if (cBeginOffset <= cOffset && cOffset < cEndOffset) {
						const char ch = pSource [cOffset - cBeginOffset];

						// Hex.
						AddHex8 (ch, strHexData + cPos);

						// Text.
						strText [c] = ch > 32 ? ch : '.';
					} else {
						strHexData [cPos + 0] = ' ';
						strHexData [cPos + 1] = ' ';
						strText [c] = ' ';
					}
					strHexData [cPos + 2] = ' ';
					cPos += 3;

					++ cOffset;
				}

				// Delimiter.
				strHexData [cPos ++] = ' ';
				strHexData [cPos ++] = ' ';
				assert (strHexData + cPos == strText);

				cPos += 16;

				strHexData [cPos ++] = '\r';
				strHexData [cPos ++] = '\n';
			}

			assert (cPos < cLineLen*cLines + 1);
			strHexData [cPos] = '\0';
		}

		::SetDlgItemText (_hWnd, _id, strHexData);

		// Scroll to the line.
		const size_t cSelBegin = ((cOffset - cBeginOffset)/16)*cLineLen + 8 + 3 + ((cOffset - (cBeginOffset/16)*16) & 0xF)*3;
		const size_t cLine = ::SendDlgItemMessage (_hWnd, _id, EM_LINEFROMCHAR, cSelBegin, 0);
		::SendDlgItemMessage (_hWnd, _id, EM_LINESCROLL, 0, cLine > 5 ? cLine - 5 : 0);

		// Select the line.
		::SendDlgItemMessage (_hWnd, _id, EM_SETSEL, cSelBegin, cSelBegin + 2);
	} else {
		//
		// Text file.
		//

		size_t cCurOffset = cOffset - cBeginOffset;

		// Convert possible Unix-style new lines to Windows style.
		char * pSource2 = NULL;
		size_t cLFs = 0;
		for (size_t c = 0; c < cDataSize; ++ c) {
			if (pSource [c] == '\n' && (c == 0 || pSource [c - 1] != '\r'))
				++ cLFs;
		}
		if (cLFs != 0)
			pSource2 = new char [cDataSize + cLFs + 1];
		CArrayHolder<char> pSource2_Holder (pSource2);
		if (cLFs != 0) {
			size_t c2 = 0;
			for (size_t c1 = 0; c1 < cDataSize; ++ c1) {
				if (pSource [c1] == '\n' && (c1 == 0 || pSource [c1 - 1] != '\r')) {
					if (c2 < cCurOffset)
						++ cCurOffset;

					pSource2 [c2 ++] = '\r';
					pSource2 [c2 ++] = '\n';
				} else
					pSource2 [c2 ++] = pSource [c1];
			}
			assert (c2 == cDataSize + cLFs);
			pSource2 [c2] = '\0';
		}

		::SetDlgItemText (_hWnd, _id, pSource2 ? pSource2 : pSource);

		// Scroll to the line.
		const size_t cLine = ::SendDlgItemMessage (_hWnd, _id, EM_LINEFROMCHAR, cCurOffset, 0);
		::SendDlgItemMessage (_hWnd, _id, EM_LINESCROLL, 0, cLine > 5 ? cLine - 5 : 0);

		// Select the line.
		const size_t cSelBegin = ::SendDlgItemMessage (_hWnd, _id, EM_LINEINDEX, cLine, 0);
		const size_t cSelSize = ::SendDlgItemMessage (_hWnd, _id, EM_LINELENGTH, cCurOffset, 0);
		::SendDlgItemMessage (_hWnd, _id, EM_SETSEL, cSelBegin, cSelBegin + cSelSize + 1);
	}
}

void ShowFileOffsetCtl (HWND _hWnd, int _id, const char * _strFileName, size_t _cOffset, size_t _cLen) {
	_ShowFileCtl (_hWnd, _id, _strFileName, false, _cOffset, _cLen);
}

void ShowFileLineCtl (HWND _hWnd, int _id, const char * _strFileName, size_t _cLine, size_t _cLen) {
	_ShowFileCtl (_hWnd, _id, _strFileName, true, _cLine, _cLen);
}

struct CSourceData {
	const char * strFileName;
	size_t cOffsetOrLine;
	bool bLine2Offset;
};

static
INT_PTR CALLBACK SourceProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			const CSourceData * const pData = reinterpret_cast<const CSourceData *> (_lParam);

			char strFileTitle [MAX_PATH + 1];
			if (::GetFileTitle (pData->strFileName, strFileTitle, MAX_PATH) != 0)
				::strcpy (strFileTitle, "No file");

			::SetWindowText (_hWnd, strFileTitle);

			_ShowFileCtl (_hWnd, IDC_SOURCE, pData->strFileName, pData->bLine2Offset, pData->cOffsetOrLine, -1);

			return TRUE;
		}

		case WM_COMMAND:
			if (LOWORD (_wParam) == IDOK || LOWORD (_wParam) == IDCANCEL) {
				// On closing dialog.
				::EndDialog (_hWnd, 0);
				return TRUE;
			}
	}

	return FALSE;
}

void ShowFileOffsetBox (HWND _hWnd, const char * _strFileName, size_t _cOffset) {
	CSourceData data = {_strFileName, _cOffset, false};
	DlgBox (IDD_SOURCE, _hWnd, & SourceProc, reinterpret_cast<LPARAM> (& data));
}

void ShowFileLineBox (HWND _hWnd, const char * _strFileName, size_t _cLine) {
	CSourceData data = {_strFileName, _cLine, true};
	DlgBox (IDD_SOURCE, _hWnd, & SourceProc, reinterpret_cast<LPARAM> (& data));
}
